package algo.implementation;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.regex.Pattern;

import memetic.operators.DiversificationNGD;
import memetic.operators.LocalSearchNGD;
import memetic.operators.NeighborhoodGeneration;
import memetic.operators.PopulationInitilization;
import funtions.NGDFitnessFunction;

public class AlgorithmExecutionNGD {

	private static Charset charset = Charset.forName("ISO8859_1");
	private static CharsetEncoder encoder = charset.newEncoder();
	private static CharsetDecoder decoder = charset.newDecoder();
	private Pattern psentInstFieldSplit=Pattern.compile("\t");
	/*
	 * 
	 */
	private NGDFitnessFunction fitnessFunct;
	private PopulationInitilization populationInit;
	private NeighborhoodGeneration neighborhoodGen;
	private LocalSearchNGD localSearch;
	private DiversificationNGD diversification;
	/*
	 * Parameters for the Fitness Function
	 */
	private String [] sentLengthSet;
	
	/**
	 * This method receives a String returns the ByteBuffer that represents that String.
	 * @param String String to be transformed.
	 * @return ByteByffer ByteBuffer representation of the String. 
	 */
	public ByteBuffer stringToBBuffer(String msg){
		try{
		    return encoder.encode(CharBuffer.wrap(msg));
		  }catch(Exception e){e.printStackTrace();}
		  return null;
	}
	/**
	 * This method receives a ByteBuffer and returns the String that it represents.
	 * @param ByteBuffer ByteBuffer to be transformed.
	 * @return String String representation of the ByteBuffer. 
	 */
	public String BBuffertoString(ByteBuffer buffer){
		String data = "";
		  try{
		    int old_position = buffer.position();
		    data = decoder.decode(buffer).toString();
		    // reset buffer's position to its original so it is not altered:
		    buffer.position(old_position);  
		  }catch (Exception e){
		    e.printStackTrace();
		    return "";
		  }
		  return data;
	}
	/**
	 * This gets the current date.
	 * @param String Date format.
	 * @return String current Date. 
	 */
	public String now(String dateFormat) {
	    Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
	    return sdf.format(cal.getTime());

	  }
	/**
	 * 
	 */
	public File[] getSubFolderFromFolfer(String mainFolderPath){
		File folder=new File(mainFolderPath);
		File [] subFolders=folder.listFiles();
		return subFolders;
	}
	/**
	 * Transform a integers (primitive) array into a string separated by ","
	 */
	private String convertIntArray_String(int [] intArr){
		String returnString="";
		for(int w=0;w<intArr.length;w++){
			if(w==intArr.length-1){
				returnString+=intArr[w];
			}else{
				returnString+=intArr[w]+",";
			}
		}
		return returnString;
	}
	/**
	 * Given an individual gets the summary
	 * @throws IOException 
	 * 
	 */
	private void getFinalSummary(int[] optIndv,String resultDir,String validationFolder, String resfile) throws IOException{
		String summary="";
		for(int w=0;w<optIndv.length;w++){
			if(optIndv[w]==1){
				if(w==optIndv.length-1)
					summary+=this.psentInstFieldSplit.split(this.sentLengthSet[w])[2];
				else
					summary+=this.psentInstFieldSplit.split(this.sentLengthSet[w])[2]+" ";
			}
		}
		FileWriter fstream=new FileWriter(resultDir+"\\summary.txt",true);
		FileWriter fstream2=new FileWriter(validationFolder+"\\"+resfile+".txt",true);
		BufferedWriter out = new BufferedWriter(fstream);
		BufferedWriter out2 = new BufferedWriter(fstream2);
		out.write(summary);
		out2.write(summary);
		out.close();
		fstream.close();
		out2.close();
		fstream2.close();
	}
	/**
	 * 
	 * 
	 */
	private int[] refinement(int [] optIndv) {
		int summaryLength=this.fitnessFunct.calculateTotalLengthInd(optIndv);
		/*Max length defined for the summary
		 * 
		 */
		double iterFit=-1,maxFit=-1;
		int[] iterIndv=new int[optIndv.length];
		int[] optIterIndv=new int[optIndv.length];
		iterIndv=optIndv.clone();
		if(summaryLength>this.fitnessFunct.getMaxLength()){
			while(summaryLength>this.fitnessFunct.getMaxLength()){
				optIterIndv=optIndv.clone();
				iterFit=-1;
				maxFit=-1;
				for(int w=0;w<optIndv.length;w++){
					if (maxFit==-1&&optIndv[w]==1) {
						optIterIndv[w]=0;
						maxFit=this.fitnessFunct.calculateFitFunct(optIterIndv);
					}
					if (maxFit>-1&&optIndv[w]==1) {
						iterIndv[w]=0;
						iterFit=this.fitnessFunct.calculateFitFunct(iterIndv.clone());
						if (iterFit>maxFit) {
							optIterIndv=iterIndv.clone();
						}
						if(iterFit==maxFit&&
								this.fitnessFunct.calculateTotalLengthInd(iterIndv)
								>this.fitnessFunct.calculateTotalLengthInd(optIterIndv)){
							optIterIndv=iterIndv.clone();
						}
					}
				}
				optIndv=optIterIndv.clone();
				summaryLength=this.fitnessFunct.calculateTotalLengthInd(optIndv);
			}
		}
		System.out.println("Final maxLength: "+summaryLength);
		return optIndv;
	}
	/**
	 * Return index the best individual from the history
	 */
	public int getBestHistInd(ArrayList<Double> fitness_hist,ArrayList<Integer> len_hist){
		double max_history=fitness_hist.get(0).doubleValue();
		double max_history_len=len_hist.get(0).doubleValue();
		int max_history_pos=0;
		for (int w=1;w<fitness_hist.size();w++){
			if(w<fitness_hist.get(w).doubleValue()){
				max_history=fitness_hist.get(w).doubleValue();
				max_history_len=len_hist.get(w).doubleValue();
				max_history_pos=w;
			}
			if(w==fitness_hist.get(w).doubleValue()){
				if(max_history_len>len_hist.get(w).doubleValue()){
					max_history=fitness_hist.get(w).doubleValue();
					max_history_len=len_hist.get(w).doubleValue();
					max_history_pos=w;
				}
			}
		}
		return max_history_pos;
	}
	
	
	
	/**
	 * Algorithm Design
	 * @throws IOException 
	 */
	public void algorithmDesign_I(String resultsDir, int maxIterations, ByteBuffer outBuffer,String mainResultsDir, String validationFolder,String resfile,String folderPath) throws IOException{
		int iterationsCounter=0;
		this.fitnessFunct=new NGDFitnessFunction();
		this.fitnessFunct.init(folderPath);
		this.sentLengthSet=this.fitnessFunct.sentLengthSet;
		System.out.println("Precalculando Coverage");
		this.fitnessFunct.initCoverage();
		System.out.println("Hecho");
		int repeticions=0;
		
		//
		File controlFile;
		FileOutputStream fosControl;
		FileChannel control;
		//
		String controlVal_resfile=resfile;
		
		String resulsDir;
		
		while (repeticions<30){
			
			//
			resulsDir=mainResultsDir+"\\"+
				now("yyyy-MM-dd'_at_H='hh'_m='mm'_s='ss").trim();
			new File(resulsDir).mkdir();
			//
			
			System.out.println("_____________\nRepeticion: "+repeticions);
			iterationsCounter=0;
			controlFile=new File(resulsDir+"\\control.txt");
			fosControl = new FileOutputStream(controlFile);
			control=fosControl.getChannel();
			resfile=controlVal_resfile;
			resfile=resfile+"_"+repeticions;
			/*
			 * Initialization
			 */
			this.populationInit=new PopulationInitilization();
			int [] initInd=this.populationInit.initOneInd_random_PercPop(this.sentLengthSet.length, 1f);
			int [] iterIndividual=new int[initInd.length];
			int [] optIndividual=new int[initInd.length];
		
			//This attributes was created to store the history
			ArrayList<Double> fitness_history=new ArrayList<Double>();
			ArrayList<Integer> len_history=new ArrayList<Integer>();
			ArrayList<int[]> ind_history=new ArrayList<int[]>();
			//
		
			double fitnessValue=this.fitnessFunct.calculateFitFunct(initInd);
			double fitnessVariation=0,fit_I=0,fit_II=0;
			boolean considerDiver=false;
			int iterDiversification=0;
			//
		
			this.neighborhoodGen=new NeighborhoodGeneration();
			this.localSearch=new LocalSearchNGD(this.fitnessFunct);
			this.diversification=new DiversificationNGD();
			String controlString="";
			this.diversification.init(this.fitnessFunct);
			//
			controlString+=iterationsCounter+"\t"+"INIT"+"\t"+convertIntArray_String(initInd)+"\t"+fitnessValue+"\t"+
					this.fitnessFunct.calculateTotalLengthInd(initInd)+"\n";
			outBuffer=stringToBBuffer(controlString);
		
			//Add to history
			fitness_history.add(new Double(fitnessValue));
			ind_history.add(initInd.clone());
			len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(initInd)));
			//
		
			while(iterationsCounter<=maxIterations){
			
				System.out.println("Iteration: "+iterationsCounter);
				/*
				 * Local search
				 */
				//this.neighborhoodGen.hammingDistance_one(initInd);
				//Hamming distance 1
				iterIndividual=this.localSearch.greedyLocalSearch(initInd, this.neighborhoodGen.hammingDistance_one(initInd));
				iterationsCounter=iterationsCounter+this.localSearch.neighborhood.length;
				fit_I=this.fitnessFunct.calculateFitFunct(initInd);
				fit_II=this.fitnessFunct.calculateFitFunct(iterIndividual);
			
				//Add to history
				fitness_history.add(new Double(fit_II));
				ind_history.add(iterIndividual.clone());
				len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(iterIndividual)));
				//
			
				fitnessVariation=Math.abs(fit_I-fit_II);
				controlString+=iterationsCounter+"\t"+"LS-H1"+"\t"+convertIntArray_String(iterIndividual)+"\t"+fit_II+
						"\t"+this.fitnessFunct.calculateTotalLengthInd(iterIndividual)+"\n";
				/*
				 *Diversification policy
				 */
				if(fitnessVariation<3500){
					considerDiver=true;
				}
				if(considerDiver){
					iterDiversification=iterDiversification+this.localSearch.neighborhood.length;
				}else{
					iterDiversification=0;
				}
				if(considerDiver&&iterDiversification>(maxIterations*0.03)){
					initInd=this.diversification.mutation_percent(initInd.clone(), 3);
					iterationsCounter=iterationsCounter+1;
				
					fit_II=this.fitnessFunct.calculateFitFunct(initInd);
				
					//Add to history
					fitness_history.add(new Double(fit_II));
					ind_history.add(initInd.clone());
					len_history.add(new Integer(this.fitnessFunct.calculateTotalLengthInd(initInd)));
					//
				
					controlString+=iterationsCounter+"\t"+"M"+"\t"+convertIntArray_String(initInd)+"\t"
							+fit_II+"\t"+this.fitnessFunct.calculateTotalLengthInd(initInd)+"\n";
				}else{
					initInd=iterIndividual;
				}
				/*
				 * End diversification policy
				 */
				outBuffer=stringToBBuffer(controlString);
				if(outBuffer.limit()>(5*1024*1024)-(2*1024)){
					System.out.println("Writing control file...");
					control.write(outBuffer);
					outBuffer.flip();
					outBuffer.clear();
				}
			}
			control.write(outBuffer);
			//Refinement
			control.close();
			System.out.println("Length: "+this.fitnessFunct.calculateTotalLengthInd(iterIndividual));
			System.out.println("Refinando...");
		
			//get the best from history
			int bestPos=getBestHistInd(fitness_history, len_history);
			iterIndividual=ind_history.get(bestPos).clone();
			//
		
			optIndividual=refinement(iterIndividual.clone());
			getFinalSummary(optIndividual, resulsDir,validationFolder,resfile);
			//
			repeticions++;
		}
	}
	
	/**
	 * Main
	 */
	public static void main(String[] args) throws IOException,Exception {
		//
		int totalNumberIterations=15000;
		//
		AlgorithmExecutionNGD algorithmExecution=new AlgorithmExecutionNGD();
		String mainResultsDir="C:\\Users\\zebastian\\Desktop\\Curso_Dirigido\\algo_results";
		String sourceDir="C:\\Users\\zebastian\\Desktop\\Curso_Dirigido\\aux_gen_files";
		File [] foldersArray=algorithmExecution.getSubFolderFromFolfer(sourceDir);
		//
		String resulsDir;
		//
		/*
		 * Variable for results storing and traceability
		 */
		ByteBuffer outResBuf = ByteBuffer.allocateDirect(1*1024*1024);
		//
			
		for(int folCount=0;folCount<foldersArray.length;folCount++){
			
			//For each dataSet

			//Create a new result directory every time it is executed
			resulsDir=mainResultsDir+"\\"+
					foldersArray[folCount].getName();
			if(!new File(resulsDir).exists()){
				new File(resulsDir).mkdir();
			}
			//resulsDir=mainResultsDir+"\\"+
			//		foldersArray[folCount].getName()+"\\"+
			//	algorithmExecution.now("yyyy-MM-dd'_at_H='hh'_m='mm'_s='ss").trim();
			//new File(resulsDir).mkdir();
			//End creation of result directories
			/*
		 	* Algorithm Design
		 	*/
			//algorithmExecution.algorithmDesign_I(resulsDir,totalNumberIterations,outResBuf,channelControl,mainResultsDir+"\\systems",foldersArray[folCount].getName()+"_"+repeticions,foldersArray[folCount].getAbsolutePath());
			algorithmExecution.algorithmDesign_I(resulsDir,totalNumberIterations,outResBuf,resulsDir,mainResultsDir+"\\systems",foldersArray[folCount].getName(),foldersArray[folCount].getAbsolutePath());
			/*
			 * End Algorithm Design
			 */
		}
	}
}
